#    Copyright 2008, 2009 Tri-County Electric Cooperative, St. Matthews, South Carolina
#    This file is part of COOPGIS.
#    COOPGIS is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#    COOPGIS is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    You should have received a copy of the GNU General Public License
#    along with COOPGIS.  If not, see <http://www.gnu.org/licenses/>.
#************************BEGIN Add Include Files to Path************************
import sys
NeededPaths=['/opt/coopgis/djangoproject/include', '/opt/coopgis/djangoproject']
for path in NeededPaths:
    if(not path in sys.path):
        sys.path.append(path)
#************************END   Add Include Files to Path************************
import cis, command, compatibleunit, geography, oms, staker, symbology, unsorted
import COOPGIS_Ajax, COOPGIS_GPS, COOPGIS_Misc, COOPGIS_ReportLab, COOPGIS_String, COOPGIS_Update
import os, csv, shutil, datetime, time, reportlab, djangoproject.settings
from django.contrib.gis.db import models
from django.contrib.gis.geos import *
from django.http import HttpResponse
from django.contrib import admin
import math
import _mysql as mysql





def CreateApplicationPermission(UserName, ApplicationName):
    applicationPermission=ApplicationPermission()
    applicaitonPermission = applicationPermission.GetOrCreate(UserName, ApplicationName)
    return applicationPermission

class CurrentSelection(models.Model):
    IPAddress=models.IPAddressField()  #Avoid Having to Log in by tracking selections by IP Address
    Station=models.ForeignKey('Station', null=True, blank=True)
    Span=models.ForeignKey('Span', null=True, blank=True)
    class Meta:
        db_table=u'coopgiscurrentselection'
        app_label='djangoproject'

class SelectionPoint(models.Model):
    Point=models.PointField(db_column='the_geom', srid=djangoproject.settings.COOPGISSRID)
    UserName=models.CharField(max_length=250)
    objects=models.GeoManager()
    class Meta:
        db_table=u'coopgisselectionpoint'
        app_label='djangoproject'
    def Create(self, point, UserName):
        New=SelectionPoint()
        New.Point=point
        New.save()
    
class SelectionLine(models.Model):
    Line=models.LineStringField(db_column='the_geom', srid=djangoproject.settings.COOPGISSRID)
    UserName=models.CharField(max_length=250)
    objects=models.GeoManager()
    class Meta:
        db_table=u'coopgisselectionline'
        app_label='djangoproject'
    def Create(self, line, UserName):
        New=SelectionLine()
        New.Line=line
        New.save()

class CircuitTrace(models.Model):
    """CircuitTrace is a symbology oriented calculated class that traces a circuit from the current station"""
    Line=models.LineStringField(db_column='the_geom', srid=djangoproject.settings.COOPGISSRID)
    UserName=models.TextField()
    CircuitNumber=models.TextField()
    PhaseCode=models.TextField()
    AfterStartTagIndicator=models.NullBooleanField(null=True, blank=True, db_column='afterstarttagindicator')
    AfterEndTagIndicator=models.NullBooleanField(null=True, blank=True, db_column='afterendtagindicator')
    Span=models.ForeignKey('Span')
    objects=models.GeoManager()
    class Meta:
        db_table=u'coopgiscircuittrace'
        app_label='djangoproject'
    def PerformTrace(self, CircuitNumber, UserName, StartTagNumber='', EndTagNumber=''):
        """PerformTrace Traces a specified Circuit Number From the Substation that matches the circuit number downstream"""
        CircuitTrace.objects.filter(UserName=UserName).delete()
        Substations=compatibleunit.Assembly.objects.filter(CompatibleUnit__SubstationIndicator=True)
        Substation=None
        for substation in Substations:
            Spans1=geography.Span.objects.filter(StartStation=substation.Station)
            Spans2=geography.Span.objects.filter(EndStation=substation.Station)
            Spans=[]
            for span in Spans1:
                Spans.append(span)
            for span in Spans2:
                Spans.append(span)
            for span in Spans:
                if(compatibleunit.Assembly.objects.filter(Span=span).filter(CircuitNumber=CircuitNumber)):
                    Substation=substation
                    break
            if(Substation):
                break
        if(not Substation):
            return 'Circuit:  ' + str(CircuitNumber) + ' was not found after searching all substation feeders'
        StationsAlreadyVisited=[]
        StationsAlreadyVisited.append(Substation.Station.id)
        SpansAlreadyVisited=[]
        self.TraceDownstream(CircuitNumber, UserName, Substation.Station, StationsAlreadyVisited, SpansAlreadyVisited, StartTagNumber, EndTagNumber)
        return 'Trace Successfully Completed for Circuit #' + str(CircuitNumber)
    def TraceDownstream(self, CircuitNumber, UserName, CurrentStation, StationsAlreadyVisited, SpansAlreadyVisited, StartTagNumber, 
                        EndTagNumber, StartTagNumberFoundIndicator=False, EndTagNumberFoundIndicator=False):
        """TraceDownstream starts at the specified station and traces out everything downstream that is connected"""
        while(True):
            if(StartTagNumber and not StartTagNumberFoundIndicator):
                if(StartTagNumber in CurrentStation.GetTagNumbers()):
                    StartTagNumberFoundIndicator=True
            elif(not StartTagNumber):
                StartTagNumberFoundIndicator=True
            if(EndTagNumber and not EndTagNumberFoundIndicator):
                if(EndTagNumber in CurrentStation.GetTagNumbers()):
                    EndTagNumberFoundIndicator=True
            PotentialSpans=self.GetNearbyUnvisitedSpans(CurrentStation, SpansAlreadyVisited)
            PotentialSpans=self.RemoveMissingCircuitSpans(PotentialSpans, CircuitNumber)
            ActualSpans=self.RemoveProtectionDeviceBlockedSpans(PotentialSpans, CurrentStation, CircuitNumber)
            if(not ActualSpans):
                return StationsAlreadyVisited, SpansAlreadyVisited
            if(len(ActualSpans) == 1):
                New=CircuitTrace()
                New.Line=ActualSpans[0].Line
                New.UserName=UserName
                New.CircuitNumber=CircuitNumber
                New.Span=ActualSpans[0]
                New.AfterStartTagIndicator=StartTagNumberFoundIndicator
                New.AfterEndTagIndicator=EndTagNumberFoundIndicator
                New.save()
                if(not ActualSpans[0].id in SpansAlreadyVisited):
                    SpansAlreadyVisited.append(ActualSpans[0].id)
                if(not CurrentStation.id in StationsAlreadyVisited):
                    StationsAlreadyVisited.append(CurrentStation.id)
                CurrentStation = ActualSpans[0].FarStation(CurrentStation)
            else:
                for i in ActualSpans:
                    New=CircuitTrace()
                    New.Line=i.Line
                    New.UserName=UserName
                    New.CircuitNumber=CircuitNumber
                    New.Span=i
                    New.AfterStartTagIndicator=StartTagNumberFoundIndicator
                    New.AfterEndTagIndicator=EndTagNumberFoundIndicator
                    New.save()
                    if(not ActualSpans[0].id in SpansAlreadyVisited):
                        SpansAlreadyVisited.append(ActualSpans[0].id)
                    if(not CurrentStation.id in StationsAlreadyVisited):
                        StationsAlreadyVisited.append(CurrentStation.id)
                    NewStation = i.FarStation(CurrentStation)
                    self.TraceDownstream(CircuitNumber, UserName, NewStation, StationsAlreadyVisited, SpansAlreadyVisited,
                                         StartTagNumber, EndTagNumber, StartTagNumberFoundIndicator, EndTagNumberFoundIndicator)
    def PerformGeoTrace(self, StartStation, SpansToStopAt, UserName):
        """PerformGeoTrace Traces from the current circuit, stopping at specified spans and any open assemblies, 
           does not look at assemblies except for open ones"""
        CircuitTrace.objects.filter(UserName=UserName).delete()
        StationsAlreadyVisited=[]
        StationsAlreadyVisited.append(StartStation.id)
        SpansAlreadyVisited=[]
        self.GeoTraceDownstream(UserName, StartStation, StationsAlreadyVisited, SpansAlreadyVisited, SpansToStopAt)
        return StationsAlreadyVisited, SpansAlreadyVisited
    def GeoTraceDownstream(self, UserName, CurrentStation, StationsAlreadyVisited, SpansAlreadyVisited, SpansToStopAt):
        """TraceDownstream starts at the specified station and traces out everything downstream that is connected"""
        while(True):
            PotentialSpans=self.GetNearbyUnvisitedSpans(CurrentStation, SpansAlreadyVisited)
            ActualSpans=self.RemoveOpenSpans(PotentialSpans, CurrentStation)
            ActualSpans=self.RemoveSpansToStopAt(ActualSpans, SpansToStopAt)
            for span in ActualSpans:  #Remove any spans that are open because they are being protected by the far station
                ActualSpans=self.RemoveOpenSpans(ActualSpans, span.FarStation(CurrentStation))
            if(not ActualSpans):
                StationsAlreadyVisited.append(CurrentStation.id)
                return StationsAlreadyVisited, SpansAlreadyVisited
            if(len(ActualSpans) == 1):
                New=CircuitTrace()
                New.Line=ActualSpans[0].Line
                New.UserName=UserName
                New.CircuitNumber=''
                New.Span=ActualSpans[0]
                New.save()
                if(not ActualSpans[0].id in SpansAlreadyVisited):
                    SpansAlreadyVisited.append(ActualSpans[0].id)
                if(not CurrentStation.id in StationsAlreadyVisited):
                    StationsAlreadyVisited.append(CurrentStation.id)
                CurrentStation = ActualSpans[0].FarStation(CurrentStation)
            else:
                for i in ActualSpans:
                    New=CircuitTrace()
                    New.Line=i.Line
                    New.UserName=UserName
                    New.CircuitNumber=''
                    New.Span=i
                    New.save()
                    if(not ActualSpans[0].id in SpansAlreadyVisited):
                        SpansAlreadyVisited.append(ActualSpans[0].id)
                    if(not CurrentStation.id in StationsAlreadyVisited):
                        StationsAlreadyVisited.append(CurrentStation.id)
                    NewStation = i.FarStation(CurrentStation)
                    self.GeoTraceDownstream(UserName, NewStation, StationsAlreadyVisited, SpansAlreadyVisited, SpansToStopAt)
    def GetNearbyUnvisitedSpans(self, Station, SpansAlreadyVisited):
        Spans1=geography.Span.objects.filter(StartStation=Station)
        Spans2=geography.Span.objects.filter(EndStation=Station)
        SpanList=[]
        for i in Spans1:
            if(not i.id in SpansAlreadyVisited):
                SpanList.append(i)
        for i in Spans2:
            if(not i.id in SpansAlreadyVisited): 
                SpanList.append(i)
        return SpanList
    def RemoveProtectionDeviceBlockedSpans(self, ValidSpans, Station, CircuitNumber):
        pass #ToDo
        return ValidSpans
    def RemoveOpenSpans(self, Spans, station):
        SpansToReturn=[]
        for span in Spans:
            SpansToReturn.append(span)
        OpenAssemblies=compatibleunit.Assembly.objects.filter(Station=station).filter(OpenIndicator=True)
        if(OpenAssemblies):
            for openAssembly in OpenAssemblies:
                ProtectedSpans=compatibleunit.ProtectingAssembly.objects.filter(Assembly=openAssembly)
                for protectedSpan in ProtectedSpans:
                    try:
                        SpansToReturn.remove(protectedSpan.Span)
                    except ValueError:
                        pass
        return SpansToReturn             
    def RemoveSpansToStopAt(self, spans, SpansToStopAt):
        NewSpans=[]
        for i in spans:
            if not i in SpansToStopAt:
                NewSpans.append(i)
        return NewSpans
    def RemoveMissingCircuitSpans(self, PotentialSpans, CircuitNumber):
        NewPotentialSpans=[]
        for i in PotentialSpans:
            FoundMatch=False
            assemblies=compatibleunit.Assembly.objects.filter(Span=i).filter(CircuitNumber=CircuitNumber)
            if(assemblies):
                NewPotentialSpans.append(i)
        return NewPotentialSpans
        
class ApplicationDefaultMap(models.Model):
    """ApplicationDefaultMap holds the default maps for each user for each application
       the master default is an entry with a blank username"""
    UserName=models.CharField(max_length=250)   #Could also be an IP Address field if not logged in 
    ApplicationName=models.CharField(max_length=250)
    Map=models.ForeignKey("Map")
    class Meta:
        db_table=u'coopgisapplicationdefaultmap'
        app_label='djangoproject'
    def GetOrCreate(self, UserName, ApplicationName, Map):
        if(not ApplicationName in djangoproject.settings.COOPGISAllApplications):
            raise('Tried to GetOrCreate an ApplicationDefaultMap for an invalid application:  ' + str(ApplicationName))
        if(not UserName and Map):  #This represents setting up a default for the application
            try:
                New=ApplicationDefaultMap.objects.filter(UserName__exact='', ApplicationName__exact=ApplicationName)[0]
            except:
                New=ApplicationDefaultMap()
            New.UserName=''
            New.ApplicationName=ApplicationName
            New.Map=Map
            New.save()
            return New 
        if(not UserName and not Map):  #This should not happen, represents setting the default map for an application to None
            raise('In ApplicationDefaultMap.GetOrCreate, UserName and Map were both False')     
        #UserName is True after this point, so no need to check it
        if(Map):  #A UserName has been specified.  If a Map is Specified, set the user to use that map by default
            try:
                New=ApplicationDefault.objects.filter(UserName__exact=UserName, ApplicationName__exact=ApplicationName)[0]
            except:
                New=ApplicationDefaultMap()
                New.UserName=UserName
            New.Map=Map
            New.save()
            return New
        #Otherwise, a map is not specified, so set the User to use the default map by default explicitly
        try:
            New=ApplicationDefault.objects.filter(UserName__exact=UserName, ApplicationName__exact=ApplicationName)[0]
        except:
            New=ApplicationDefaultMap()
            New.UserName=UserName
            New.ApplicationName=ApplicationName
        New.Map=self.GetDefaultMap(UserName='', ApplicationName=ApplicationName)
        New.save()
        return New
    def GetDefaultMap(self, UserName, ApplicationName):
        if(not ApplicationName in ValidApplicationNames):
            raise('Tried to GetDefaultMap for an invalid application:  ' + str(ApplicationName))
        try:  #Try to return the default for the user
            return (ApplicationDefaultMap.objects.filter(UserName__exact=UserName, ApplicationName__exact=ApplicationName)[0].Map)
        except:
            pass
        try: #Try to return the default for the application
            return (ApplicationDefaultMap.objects.filter(UserName__exact='', ApplicationName__exact=ApplicationName)[0].Map)
        except:
            pass
        raise('No Default Map for:  ' + str(ApplicationName))

class Map(models.Model):
    """Map Stores the Textual Representation of a Map File."""
    Name=models.CharField(max_length=250, db_column='name')  #This is both the Display and Map Name 
    UserName=models.CharField(max_length=250, db_column='username') #Each map is assigned to a user
    LastModifyDate=models.DateField(db_column='lastmodifydate')
    CreateDate=models.DateField(db_column='createdate')
    Content=models.TextField(db_column='content')
    class Meta:
        db_table=u'coopgismap'
        app_label='djangoproject'
    def GetOrCreate(self, Name, UserName, Content):
        try:
            New=Map.objects.filter(Name__exact=Name)[0]
        except:
            New=Map()
            New.UserName=UserName
            New.CreateDate=datetime.datetime.now()
        New.Name=Name
        New.LastModifyDate=datetime.datetime.now()
        New.Content=Content
        New.save()
        return New
    def GetMapFile(self, UserName='', stakerWorkOrder='', MapReportIdentifier=''):
        if(stakerWorkOrder and stakerWorkOrder.DisplayMapOutlineIndicator):
            StakerWorkOrderNumber=str(stakerWorkOrder.id)
        else:
            StakerWorkOrderNumber='-1'
        Temp=str(self.Content)
        MapTemplates=MapTemplate.objects.all()
        for i in MapTemplates:  # Comment lines are a quick fix, split out as rows and comment each line if needed in future
            Temp=Temp.replace('#{{' + i.Tag + '}}', '') #remove commented out tags, '#' must immediately preceed the tag
            Temp=Temp.replace('# {{' + i.Tag + '}}', '') #remove commented out tags, '#' must immediately preceed the tag
            Temp=Temp.replace('#  {{' + i.Tag + '}}', '') #remove commented out tags, '#' must immediately preceed the tag
            Temp=Temp.replace('#   {{' + i.Tag + '}}', '') #remove commented out tags, '#' must immediately preceed the tag
            Temp=Temp.replace('#    {{' + i.Tag + '}}', '') #remove commented out tags, '#' must immediately preceed the tag
            Temp=Temp.replace('{{' + i.Tag + '}}', i.Content)
        Temp=Temp.replace('{{Database}}', djangoproject.settings.COOPGISMapFileDBLines)
        Temp=Temp.replace('{{UserName}}', UserName)
        Temp=Temp.replace('{{StakerWorkOrderNumber}}', StakerWorkOrderNumber)
        Temp=Temp.replace('{{MapReportIdentifier}}', MapReportIdentifier)
        return Temp
    def __unicode__(self):        
        return self.Name
    def GenerateMapFile(self, UserName, ApplicationName, stakerWorkOrder='', MapReportIdentifier=''):
        FileName=djangoproject.settings.COOPGISMapFilePath + UserName + '_' + ApplicationName  + ".map"
        try:
            os.remove(FileName)
        except:
            if(os.path.exists(FileName)):
                raise("Could not remove the old Map File to Generate the new one:  " + FileName)
        File=open(FileName, 'wb')
        File.write(self.GetMapFile(UserName, stakerWorkOrder=stakerWorkOrder, MapReportIdentifier=MapReportIdentifier))
        File.close()
        return FileName
    def GetLayerName(self):
        Temp=str(self.Content)
        MapTemplates=MapTemplate.objects.all()
        for i in MapTemplates:
            Temp=Temp.replace('{{' + i.Tag + '}}', i.Content)

        StartIndex=Temp.find('LAYER') + len('LAYER') + 1
        EndIndex=Temp[StartIndex:].find('TYPE') + StartIndex
        try:
            LayerName=Temp[StartIndex:EndIndex]
        except:
            return 'No Layer Name Found in GetLayerName'
        LayerName=LayerName.replace("'", '')
        StartIndex=LayerName.find('NAME') + len('NAME') 
        LayerName=LayerName[StartIndex:]
        LayerName=LayerName.strip()
        return LayerName

class MapTemplate(models.Model):
    """MapTemplate is a dictionary of codes that can be used in a map, replaced with the specified text"""
    Name=models.CharField(max_length=250, db_column = 'name')
    Tag=models.CharField(max_length=250, db_column = 'tag') #{{Tag}} is replaced by Content for each tag, {{UserName}} and {{Database}} are special tags defined separately
    UserName=models.CharField(max_length=250, db_column = 'username') 
    LastModifyDate=models.DateField(db_column='lastmodifydate')
    CreateDate=models.DateField(db_column='createdate')
    Content=models.TextField(db_column='content')
    class Meta:
        db_table=u'coopgismaptemplate'
        app_label='djangoproject'
    def GetOrCreate(self, Name, UserName, Tag, Content):
        if(Tag == "UserName" or Tag == "Database" or Tag == 'MapReportIdentifier' or Tag == 'StakerWorkOrderNumber'):
            raise("UserName, Database, MapReportIdentifier, and StakerWorkOrderNumber are reserved tag names (in MapTemplate GetOrCreate)")
        try:
            New=MapTemplate.objects.filter(Name__exact=Name)[0]
        except:
            New=MapTemplate()
            New.UserName=UserName
            New.CreateDate=datetime.datetime.now()
        New.Name=Name
        New.Tag=Tag
        New.LastModifyDate=datetime.datetime.now()
        New.Content=Content
        New.save()
        return New


class COOPGISConfig(models.Model):
    """Holds Name/Value pairs with general application configuration information"""
    Name=models.CharField(max_length=250, db_column='name', db_index=True, unique=True)
    Description=models.TextField(db_column='description')
    Value=models.TextField(db_column='value')
    ValidUnits = ['km', 'mi', 'm', 'yd', 'ft', 'survey_ft', 'inch', 'cm', 'mm','um', 'british_ft', 'british_yd', 'british_chain_sears', 'indian_yd',
                  'sears_yd', 'clarke_ft', 'chain', 'chain_benoit', 'chain_sears', 'british_chain_benoit',' british_chain_sears_truncated', 
                  'gold_coast_ft', 'link', 'link_benoit', 'link_sears', 'clarke_link', 'fathom', 'rod', 'nm', 'nm_uk', 'german_m']
    objects=models.GeoManager()
    def __unicode__(self):
        return self.Name + ":  " + self.Value
    class Meta:
        db_table=u'coopgisconfig'
        app_label='djangoproject'
    @staticmethod
    def GetDefaultBounds():
        config = COOPGISConfig.objects.filter(Name = 'DefaultBounds').get()
        Results = []
        for i in config.Value.split(','):
            Results.append(float(i))
        #Convert from SRID 4326 to 900913 
        Results[0] = Results[0] * 20037508.34 / 180
        Results[1] = (math.log(math.tan((90 + Results[1]) * math.pi / 360)) / (math.pi / 180)) * 20037508.34 / 180
        Results[2] = Results[2] * 20037508.34 / 180
        Results[3] = (math.log(math.tan((90 + Results[3]) * math.pi / 360)) / (math.pi / 180)) * 20037508.34 / 180
        return tuple(Results)
    @staticmethod
    def GetDefaultCenter():
        config = COOPGISConfig.objects.filter(Name = 'DefaultCenter').get()
        Results = []
        for i in config.Value.split(','):
            Results.append(float(i))
        #Convert from SRID 4326 to 900913 
        Results[0] = Results[0] * 20037508.34 / 180
        Results[1] = (math.log(math.tan((90 + Results[1]) * math.pi / 360)) / (math.pi / 180)) * 20037508.34 / 180
        return tuple(Results)
    @staticmethod
    def GetShortDistanceUnit():
        config = COOPGISConfig.objects.filter(Name = 'ShortDistanceUnit').get()
        if(config.Value in COOPGISConfig.ValidUnits):
            return config.Value
        raise(Exception('Invalid ShortDistanceUnit'))
    @staticmethod
    def GetLongDistanceUnit():
        config = COOPGISConfig.objects.filter(Name = 'LongDistanceUnit').get()
        if(config.Value in COOPGISConfig.ValidUnits):
            return config.Value
        raise(Exception('Invalid LongDistanceUnit'))
    @staticmethod
    def GetMinimumDistanceBetweenStations():
        config = COOPGISConfig.objects.filter(Name = 'MinimumDistanceBetweenStations').get()
        return float(config.Value)
    @staticmethod
    def GetMaximumDistanceToFindNearbyStations():
        config = COOPGISConfig.objects.filter(Name = 'MaximumDistanceToFindNearbyStations').get()
        return float(config.Value)
    @staticmethod
    def GetMaximumDistanceToFindNearbyRoads():
        config = COOPGISConfig.objects.filter(Name = 'MaximumDistanceToFindNearbyRoads').get()
        return float(config.Value)



try:
    admin.site.register(COOPGISConfig)
except:
    pass
